package bma.common.langutil.pipeline;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;

import bma.common.langutil.concurrent.NoLock;
import bma.common.langutil.core.ClassUtil;

/**
 * 通用通道队列的基础实现
 * 
 * @author 关中
 * @since 1.0 (imported from BMA.ESP Project)
 * 
 * @param <TYPE>
 * @param <CTX_TYPE>
 */
public abstract class CommonPipelineAbstract<TYPE, CTX_TYPE extends CommonPipelineContext<TYPE>>
		implements CommonPipeline<TYPE> {

	public static class Simple<MTYPE> extends
			CommonPipelineAbstract<MTYPE, CommonPipelineContext<MTYPE>> {

		@Override
		protected CommonPipelineContext<MTYPE> newPipelineContext(
				CommonPipelineContext<MTYPE> prev,
				CommonPipelineContext<MTYPE> next, String name, MTYPE item) {
			return new CommonPipelineContext<MTYPE>(prev, next, name, item);
		};
	}

	private volatile CTX_TYPE head;
	private volatile CTX_TYPE tail;
	private final Map<String, CTX_TYPE> name2ctx = new TreeMap<String, CTX_TYPE>();

	/**
	 * Creates a new empty pipeline.
	 */
	public CommonPipelineAbstract() {
		super();
	}

	public Lock locker() {
		return NoLock.INSTANCE;
	}

	protected abstract CTX_TYPE newPipelineContext(CTX_TYPE prev,
			CTX_TYPE next, String name, TYPE item);

	@SuppressWarnings("unchecked")
	protected CTX_TYPE next(CTX_TYPE ctx) {
		return (CTX_TYPE) ctx.next;
	}

	@SuppressWarnings("unchecked")
	protected CTX_TYPE prev(CTX_TYPE ctx) {
		return (CTX_TYPE) ctx.prev;
	}

	@Override
	public void addFirst(String name, TYPE item) {
		Lock lock = locker();
		try {
			if (name2ctx.isEmpty()) {
				init(name, item);
			} else {
				checkDuplicateName(name);
				CTX_TYPE oldHead = head;
				CTX_TYPE newHead = newPipelineContext(null, oldHead, name, item);

				callBeforeAdd(newHead);

				oldHead.prev = newHead;
				head = newHead;
				name2ctx.put(name, newHead);

				callAfterAdd(newHead);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void addLast(String name, TYPE item) {
		Lock lock = locker();
		lock.lock();
		try {
			if (name2ctx.isEmpty()) {
				init(name, item);
			} else {
				checkDuplicateName(name);
				CTX_TYPE oldTail = tail;
				CTX_TYPE newTail = newPipelineContext(oldTail, null, name, item);

				callBeforeAdd(newTail);

				oldTail.next = newTail;
				tail = newTail;
				name2ctx.put(name, newTail);

				callAfterAdd(newTail);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void addBefore(String baseName, String name, TYPE item) {
		Lock lock = locker();
		lock.lock();
		try {
			CTX_TYPE ctx = getContextOrDie(baseName);
			if (ctx == head) {
				addFirst(name, item);
			} else {
				checkDuplicateName(name);
				CTX_TYPE newCtx = newPipelineContext(prev(ctx), ctx, name, item);

				callBeforeAdd(newCtx);

				ctx.prev.next = newCtx;
				ctx.prev = newCtx;
				name2ctx.put(name, newCtx);

				callAfterAdd(newCtx);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void addAfter(String baseName, String name, TYPE item) {
		CTX_TYPE ctx = getContextOrDie(baseName);
		Lock lock = locker();
		lock.lock();
		try {
			if (ctx == tail) {
				addLast(name, item);
			} else {
				checkDuplicateName(name);
				CTX_TYPE newCtx = newPipelineContext(ctx, next(ctx), name, item);

				callBeforeAdd(newCtx);

				ctx.next.prev = newCtx;
				ctx.next = newCtx;
				name2ctx.put(name, newCtx);

				callAfterAdd(newCtx);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void remove(TYPE item) {
		Lock lock = locker();
		lock.lock();
		try {
			remove(getContextOrDie(item));
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TYPE remove(String name) {
		Lock lock = locker();
		lock.lock();
		try {
			return remove(getContextOrDie(name)).getItem();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public <TT extends TYPE> TT remove(Class<TT> itemType) {
		Lock lock = locker();
		lock.lock();
		try {
			return ClassUtil.cast(itemType, remove(getContextOrDie(itemType))
					.getItem());
		} finally {
			lock.unlock();
		}
	}

	private CTX_TYPE remove(CTX_TYPE ctx) {
		if (head == tail) {
			head = tail = null;
			name2ctx.clear();
		} else if (ctx == head) {
			removeFirst();
		} else if (ctx == tail) {
			removeLast();
		} else {
			callBeforeRemove(ctx);

			CTX_TYPE prev = prev(ctx);
			CTX_TYPE next = next(ctx);
			prev.next = next;
			next.prev = prev;
			name2ctx.remove(ctx.getName());

			callAfterRemove(ctx);
		}
		return ctx;
	}

	@Override
	public TYPE removeFirst() {
		Lock lock = locker();
		lock.lock();
		try {
			if (name2ctx.isEmpty()) {
				throw new NoSuchElementException();
			}

			CTX_TYPE oldHead = head;
			if (oldHead == null) {
				throw new NoSuchElementException();
			}

			callBeforeRemove(oldHead);

			if (oldHead.next == null) {
				head = tail = null;
				name2ctx.clear();
			} else {
				oldHead.next.prev = null;
				head = next(oldHead);
				name2ctx.remove(oldHead.getName());
			}

			callAfterRemove(oldHead);

			return oldHead.getItem();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TYPE removeLast() {
		Lock lock = locker();
		lock.lock();
		try {
			if (name2ctx.isEmpty()) {
				throw new NoSuchElementException();
			}

			CTX_TYPE oldTail = tail;
			if (oldTail == null) {
				throw new NoSuchElementException();
			}

			callBeforeRemove(oldTail);

			if (oldTail.prev == null) {
				head = tail = null;
				name2ctx.clear();
			} else {
				oldTail.prev.next = null;
				tail = prev(oldTail);
				name2ctx.remove(oldTail.getName());
			}

			callBeforeRemove(oldTail);

			return oldTail.getItem();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void replace(TYPE oldItem, String newName, TYPE newItem) {
		Lock lock = locker();
		lock.lock();
		try {
			replace(getContextOrDie(oldItem), newName, newItem);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TYPE replace(String oldName, String newName, TYPE newItem) {
		Lock lock = locker();
		lock.lock();
		try {
			return replace(getContextOrDie(oldName), newName, newItem);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public <TT extends TYPE> TT replace(Class<TT> oldItemType, String newName,
			TYPE newItem) {
		Lock lock = locker();
		lock.lock();
		try {
			return ClassUtil.cast(oldItemType,
					replace(getContextOrDie(oldItemType), newName, newItem));
		} finally {
			lock.unlock();
		}
	}

	private TYPE replace(CTX_TYPE ctx, String newName, TYPE newItem) {
		if (ctx == head) {
			removeFirst();
			addFirst(newName, newItem);
		} else if (ctx == tail) {
			removeLast();
			addLast(newName, newItem);
		} else {
			boolean sameName = ctx.getName().equals(newName);
			if (!sameName) {
				checkDuplicateName(newName);
			}

			CTX_TYPE prev = prev(ctx);
			CTX_TYPE next = next(ctx);
			CTX_TYPE newCtx = newPipelineContext(prev, next, newName, newItem);

			callBeforeRemove(ctx);
			callBeforeAdd(newCtx);

			prev.next = newCtx;
			next.prev = newCtx;

			if (!sameName) {
				name2ctx.remove(ctx.getName());
				name2ctx.put(newName, newCtx);
			}

			PipelineItemLifeCycleException removeException = null;
			PipelineItemLifeCycleException addException = null;
			boolean removed = false;
			try {
				callAfterRemove(ctx);
				removed = true;
			} catch (PipelineItemLifeCycleException e) {
				removeException = e;
			}

			boolean added = false;
			try {
				callAfterAdd(newCtx);
				added = true;
			} catch (PipelineItemLifeCycleException e) {
				addException = e;
			}

			if (!removed && !added) {
				throw new PipelineItemLifeCycleException("Both "
						+ ctx.getItem().getClass().getName()
						+ ".afterRemove() and "
						+ newCtx.getItem().getClass().getName()
						+ ".afterAdd() failed");
			} else if (!removed) {
				throw removeException;
			} else if (!added) {
				throw addException;
			}
		}

		return ctx.getItem();
	}

	private void callBeforeAdd(CTX_TYPE ctx) {
		if (!(ctx.getItem() instanceof LifeCycleAwarePipelineItem)) {
			return;
		}

		LifeCycleAwarePipelineItem h = (LifeCycleAwarePipelineItem) ctx
				.getItem();

		try {
			h.beforeAdd(ctx);
		} catch (Throwable t) {
			throw new PipelineItemLifeCycleException(h.getClass().getName()
					+ ".beforeAdd() has thrown an exception; not adding.", t);
		}
	}

	private void callAfterAdd(CTX_TYPE ctx) {
		if (!(ctx.getItem() instanceof LifeCycleAwarePipelineItem)) {
			return;
		}

		LifeCycleAwarePipelineItem h = (LifeCycleAwarePipelineItem) ctx
				.getItem();

		try {
			h.afterAdd(ctx);
		} catch (Throwable t) {
			boolean removed = false;
			try {
				remove((CTX_TYPE) ctx);
				removed = true;
			} catch (Throwable t2) {
				// logger.warn("Failed to remove a item: " + ctx.getName(), t2);
			}

			if (removed) {
				throw new PipelineItemLifeCycleException(h.getClass().getName()
						+ ".afterAdd() has thrown an exception; removed.", t);
			} else {
				throw new PipelineItemLifeCycleException(
						h.getClass().getName()
								+ ".afterAdd() has thrown an exception; also failed to remove.",
						t);
			}
		}
	}

	private void callBeforeRemove(CTX_TYPE ctx) {
		if (!(ctx.getItem() instanceof LifeCycleAwarePipelineItem)) {
			return;
		}

		LifeCycleAwarePipelineItem h = (LifeCycleAwarePipelineItem) ctx
				.getItem();

		try {
			h.beforeRemove(ctx);
		} catch (Throwable t) {
			throw new PipelineItemLifeCycleException(h.getClass().getName()
					+ ".beforeRemove() has thrown an exception; not removing.",
					t);
		}
	}

	private void callAfterRemove(CTX_TYPE ctx) {
		if (!(ctx.getItem() instanceof LifeCycleAwarePipelineItem)) {
			return;
		}

		LifeCycleAwarePipelineItem h = (LifeCycleAwarePipelineItem) ctx
				.getItem();

		try {
			h.afterRemove(ctx);
		} catch (Throwable t) {
			throw new PipelineItemLifeCycleException(h.getClass().getName()
					+ ".afterRemove() has thrown an exception.", t);
		}
	}

	@Override
	public TYPE getFirst() {
		Lock lock = locker();
		lock.lock();
		try {
			CTX_TYPE head = this.head;
			if (head == null) {
				return null;
			}
			return head.getItem();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TYPE getLast() {
		Lock lock = locker();
		lock.lock();
		try {
			CTX_TYPE tail = this.tail;
			if (tail == null) {
				return null;
			}
			return tail.getItem();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TYPE get(String name) {
		Lock lock = locker();
		lock.lock();
		try {
			CTX_TYPE ctx = name2ctx.get(name);
			if (ctx == null) {
				return null;
			} else {
				return ctx.getItem();
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public <TT extends TYPE> TT get(Class<TT> itemType) {
		Lock lock = locker();
		lock.lock();
		try {
			CTX_TYPE ctx = getContext(itemType);
			if (ctx == null) {
				return null;
			} else {
				return ClassUtil.cast(itemType, ctx.getItem());
			}
		} finally {
			lock.unlock();
		}
	}

	public CTX_TYPE getContext(String name) {
		if (name == null) {
			throw new NullPointerException("name");
		}
		Lock lock = locker();
		lock.lock();
		try {
			return name2ctx.get(name);
		} finally {
			lock.unlock();
		}
	}

	public CTX_TYPE getContext(TYPE item) {
		if (item == null) {
			throw new NullPointerException("item");
		}
		Lock lock = locker();
		lock.lock();
		try {
			if (name2ctx.isEmpty()) {
				return null;
			}
			CTX_TYPE ctx = head;
			for (;;) {
				if (ctx.getItem() == item) {
					return ctx;
				}

				ctx = next(ctx);
				if (ctx == null) {
					break;
				}
			}
			return null;
		} finally {
			lock.unlock();
		}
	}

	public CTX_TYPE getContext(Class<? extends TYPE> itemType) {
		if (itemType == null) {
			throw new NullPointerException("itemType");
		}

		Lock lock = locker();
		lock.lock();
		try {
			if (name2ctx.isEmpty()) {
				return null;
			}
			CTX_TYPE ctx = head;
			for (;;) {
				if (itemType.isAssignableFrom(ctx.getItem().getClass())) {
					return ctx;
				}

				ctx = next(ctx);
				if (ctx == null) {
					break;
				}
			}
			return null;
		} finally {
			lock.unlock();
		}
	}

	public CTX_TYPE getHead() {
		return head;
	}

	public CTX_TYPE getTail() {
		return tail;
	}

	public List<TYPE> toList() {
		List<TYPE> r = new ArrayList<TYPE>(name2ctx.size());
		if (head == null)
			return r;
		CTX_TYPE ctx = head;
		for (;;) {
			r.add(ctx.getItem());
			ctx = next(ctx);
			if (ctx == null) {
				break;
			}
		}
		return r;
	}

	public Map<String, TYPE> toMap() {
		Lock lock = locker();
		lock.lock();
		try {
			Map<String, TYPE> map = new HashMap<String, TYPE>(
					name2ctx.size() * 2 + 1);
			if (name2ctx.isEmpty()) {
				return map;
			}

			CTX_TYPE ctx = head;
			for (;;) {
				map.put(ctx.getName(), ctx.getItem());
				ctx = next(ctx);
				if (ctx == null) {
					break;
				}
			}
			return map;
		} finally {
			lock.unlock();
		}
	}

	/**
	 * Returns the {@link String} representation of this pipeline.
	 */
	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append(getClass().getSimpleName());
		buf.append('{');
		CTX_TYPE ctx = head;
		for (;;) {
			buf.append('(');
			buf.append(ctx.getName());
			buf.append(" = ");
			buf.append(ctx.getItem().getClass().getName());
			buf.append(')');
			ctx = next(ctx);
			if (ctx == null) {
				break;
			}
			buf.append(", ");
		}
		buf.append('}');
		return buf.toString();
	}

	private void init(String name, TYPE item) {
		CTX_TYPE ctx = newPipelineContext(null, null, name, item);
		callBeforeAdd(ctx);
		head = tail = ctx;
		name2ctx.clear();
		name2ctx.put(name, ctx);
		callAfterAdd(ctx);
	}

	private void checkDuplicateName(String name) {
		if (name2ctx.containsKey(name)) {
			throw new IllegalArgumentException("Duplicate item name.");
		}
	}

	private CTX_TYPE getContextOrDie(String name) {
		CTX_TYPE ctx = getContext(name);
		if (ctx == null) {
			throw new NoSuchElementException(name);
		} else {
			return ctx;
		}
	}

	private CTX_TYPE getContextOrDie(TYPE item) {
		CTX_TYPE ctx = getContext(item);
		if (ctx == null) {
			throw new NoSuchElementException(item.getClass().getName());
		} else {
			return ctx;
		}
	}

	private CTX_TYPE getContextOrDie(Class<? extends TYPE> itemType) {
		CTX_TYPE ctx = getContext(itemType);
		if (ctx == null) {
			throw new NoSuchElementException(itemType.getName());
		} else {
			return ctx;
		}
	}

	@Override
	public void clear() {
		this.name2ctx.clear();
		this.head = this.tail = null;
	}
}
